﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using GDI.Annotations;

namespace GDI.Plotters
{
    [ToolboxItem(true)]
    [ToolboxBitmap(typeof(SimplePlotter))]
    public partial class SimplePlotter : UserControl, INotifyPropertyChanged
    {
        /* ------------------------------------------------------------------------------------------ */

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
        {
            var lv_Handler = PropertyChanged;
            if(lv_Handler != null)
                lv_Handler(this, new PropertyChangedEventArgs(PropertyName));
        }

        /* ------------------------------------------------------------------------------------------ */

        private double f_Xmin = -10;
        private double f_Xmax = 10;
        private double f_Ymin = -10;
        private double f_Ymax = 10;

        private float f_BorderXmin = 20;
        private float f_BorderXmax = 20;
        private float f_BorderYmin = 20;
        private float f_BorderYmax = 20;

        private readonly Arrow f_XAxe = new Arrow();
        private readonly Arrow f_YAxe = new Arrow();

        private readonly TextMarker f_XAxeMinMarker = new TextMarker();
        private readonly TextMarker f_XAxeMaxMarker = new TextMarker();
        private readonly TextMarker f_YAxeMinMarker = new TextMarker();
        private readonly TextMarker f_YAxeMaxMarker = new TextMarker();

        private readonly Line f_XAxeMarkerMin = new Line();
        private readonly Line f_XAxeMarkerMax = new Line();
        private readonly Line f_YAxeMarkerMin = new Line();
        private readonly Line f_YAxeMarkerMax = new Line();

        /* ------------------------------------------------------------------------------------------ */

        [Category("Оформление"), Description("Левый верхний угол рамки")]
        public PointF BorderLeftUp
        {
            get { return new PointF(f_BorderXmin, f_BorderYmin); }
            set
            {
                f_BorderXmin = value.X;
                f_BorderYmin = value.Y;
                OnPropertyChanged("BorderXmin");
                OnPropertyChanged("BorderYmin");
            }
        }

        [Category("Оформление"), Description("Правый нижний угол рамки")]
        public PointF BorderRightDown
        {
            get { return new PointF(f_BorderXmax, f_BorderYmax); }
            set
            {
                f_BorderXmax = value.X;
                f_BorderYmax = value.Y;
                OnPropertyChanged("BorderXmax");
                OnPropertyChanged("BorderYmax");
            }
        }

        [DefaultValue(20), Category("Оформление"), Description("Отступ рамки слева")]
        public float BorderXmin
        {
            get { return f_BorderXmin; }
            set
            {
                f_BorderXmin = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("BorderXmin");
                OnPropertyChanged("BorderRightDown");
            }
        }

        [DefaultValue(20), Category("Оформление"), Description("Отступ рамки справа")]
        public float BorderXmax
        {
            get { return f_BorderXmax; }
            set
            {
                f_BorderXmax = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("BorderXmax");
                OnPropertyChanged("BorderRightDown");
            }
        }

        [DefaultValue(20), Category("Оформление"), Description("Отступ рамки снизу")]
        public float BorderYmin
        {
            get { return f_BorderYmin; }
            set
            {
                f_BorderYmin = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("BorderYmin");
                OnPropertyChanged("BorderRightDown");
            }
        }

        [DefaultValue(20), Category("Оформление"), Description("Отступ рамки сверху")]
        public float BorderYmax
        {
            get { return f_BorderYmax; }
            set
            {
                f_BorderYmax = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("BorderYmax");
                OnPropertyChanged("BorderRightDown");
            }
        }

        [DefaultValue(-10), Category("Оформление"), Description("Минимальное значение на оси X")]
        public double Xmin
        {
            get { return f_Xmin; }
            set
            {
                f_Xmin = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("Xmin");
            }
        }

        [DefaultValue(10), Category("Оформление"), Description("Максимальное значение на оси X")]
        public double Xmax
        {
            get { return f_Xmax; }
            set
            {
                f_Xmax = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("Xmax");
            }
        }

        [DefaultValue(-10), Category("Оформление"), Description("Минимальное значение на оси Y")]
        public double Ymin
        {
            get { return f_Ymin; }
            set
            {
                f_Ymin = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("Ymin");
            }
        }

        [DefaultValue(10), Category("Оформление"), Description("Максимальное значение на оси Y")]
        public double Ymax
        {
            get { return f_Ymax; }
            set
            {
                f_Ymax = value;
                RefreshAxis();
                Invalidate();
                OnPropertyChanged("Ymax");
            }
        }

        protected float ScreenXmin { get { return ToScreen_X(f_Xmin); } set { f_Xmin = ToValue_X(value); } }
        protected float ScreenXmax { get { return ToScreen_X(f_Xmax); } set { f_Xmax = ToValue_X(value); } }
        protected float ScreenYmin { get { return ToScreen_Y(f_Ymin); } set { f_Ymin = ToValue_Y(value); } }
        protected float ScreenYmax { get { return ToScreen_Y(f_Ymax); } set { f_Ymax = ToValue_Y(value); } }

        protected double Kx { get { return (Width - f_BorderXmin - f_BorderXmax) / (f_Xmax - f_Xmin); } }
        protected double Ky { get { return (Height - f_BorderYmin - f_BorderYmax) / (f_Ymax - f_Ymin); } }

        /* ------------------------------------------------------------------------------------------ */

        private readonly PlotPoint f_Point = new PlotPoint(20f, new Pen(Color.Blue, 2)) { Smoothing = SmoothingMode.AntiAlias };

        public SimplePlotter()
        {
            SetStyle(ControlStyles.UserPaint |
                     ControlStyles.DoubleBuffer |
                     ControlStyles.ResizeRedraw |
                     ControlStyles.AllPaintingInWmPaint |
                     ControlStyles.SupportsTransparentBackColor,
                     true);

            f_XAxe.SetEventsTo(this);
            f_YAxe.SetEventsTo(this);

            f_XAxeMinMarker.VerticalAlign = VerticalAlign.Top;
            f_XAxeMinMarker.HorizontalAlign = HorizontalAlign.Center;
            f_XAxeMinMarker.Offset = new PointF(0, 5);
            f_XAxeMaxMarker.VerticalAlign = VerticalAlign.Top;
            f_XAxeMaxMarker.HorizontalAlign = HorizontalAlign.Center;
            f_XAxeMaxMarker.Offset = new PointF(0, 5);

            f_YAxeMinMarker.VerticalAlign = VerticalAlign.Center;
            f_YAxeMinMarker.HorizontalAlign = HorizontalAlign.Right;
            f_YAxeMinMarker.Offset = new PointF(-2, 0);
            f_YAxeMaxMarker.VerticalAlign = VerticalAlign.Center;
            f_YAxeMaxMarker.HorizontalAlign = HorizontalAlign.Right;
            f_YAxeMaxMarker.Offset = new PointF(-2, 0);

            f_XAxeMinMarker.SetEventsTo(this);
            f_XAxeMaxMarker.SetEventsTo(this);
            f_YAxeMinMarker.SetEventsTo(this);
            f_YAxeMaxMarker.SetEventsTo(this);

            f_XAxeMarkerMin.SetEventsTo(this);
            f_XAxeMarkerMax.SetEventsTo(this);
            f_YAxeMarkerMin.SetEventsTo(this);
            f_YAxeMarkerMax.SetEventsTo(this);

            f_Point.SetEventsTo(this);

            InitializeComponent();

            RefreshItems();
        }

        /* ------------------------------------------------------------------------------------------ */

        public float ToScreen_X(double x) { return (float)(Kx * (x - f_Xmin)) + f_BorderXmin; }
        public float ToScreen_Y(double y) { return (float)(-Ky * (y - f_Ymax)) + f_BorderYmax; }

        public double ToValue_X(float x) { return (x - f_BorderXmin) / Kx + f_Xmin; }
        public double ToValue_Y(float y) { return (f_BorderYmax - y) / Ky + f_Ymax; }

        public PointF ToScreen(PointF p) { return new PointF(ToScreen_X(p.X), ToScreen_Y(p.Y)); }
        public PointF ToValue(PointF p) { return new PointF((float)ToValue_X(p.X), (float)ToValue_Y(p.Y)); }

        public PointF ToScreen(double x, double y) { return new PointF(ToScreen_X(x), ToScreen_Y(y)); }
        public PointF ToValue(float x, float y) { return new PointF((float)ToValue_X(x), (float)ToValue_Y(y)); }

        public PointF PlotPaneLimit(PointF p)
        {
            return new PointF(
                Math.Max(ScreenXmin, Math.Min(ScreenXmax, p.X)),
                Math.Max(ScreenYmax, Math.Min(ScreenYmin, p.Y)));
        }

        /* ------------------------------------------------------------------------------------------ */

        private void RefreshAxis()
        {
            var lv_Xmin = ToScreen(f_Xmin, 0).Function(PlotPaneLimit);
            var lv_Xmax = ToScreen(f_Xmax, 0).Function(PlotPaneLimit);
            var lv_Ymin = ToScreen(0, f_Ymin).Function(PlotPaneLimit);
            var lv_Ymax = ToScreen(0, f_Ymax).Function(PlotPaneLimit);

            f_XAxe.Begin = lv_Xmin.Substract(5, 0);
            f_XAxe.End = lv_Xmax.Add(BorderXmax * 0.66f, 0);

            f_YAxe.Begin = lv_Ymin.Add(0, 5);
            f_YAxe.End = lv_Ymax.Substract(0, BorderYmax * 0.66f);

            f_XAxeMinMarker.Value = f_Xmin.ToString();
            f_XAxeMinMarker.Position = lv_Xmin;
            f_XAxeMaxMarker.Value = f_Xmax.ToString();
            f_XAxeMaxMarker.Position = lv_Xmax;
            f_YAxeMinMarker.Value = f_Ymin.ToString();
            f_YAxeMinMarker.Position = lv_Ymin;
            f_YAxeMaxMarker.Value = f_Ymax.ToString();
            f_YAxeMaxMarker.Position = lv_Ymax;

            f_XAxeMarkerMin.Begin = lv_Xmin.Add(0, 3);
            f_XAxeMarkerMin.End = lv_Xmin.Substract(0, 3);
            f_XAxeMarkerMax.Begin = lv_Xmax.Add(0, 3);
            f_XAxeMarkerMax.End = lv_Xmax.Substract(0, 3);
            f_YAxeMarkerMin.Begin = lv_Ymin.Add(3, 0);
            f_YAxeMarkerMin.End = lv_Ymin.Substract(3, 0);
            f_YAxeMarkerMax.Begin = lv_Ymax.Add(3, 0);
            f_YAxeMarkerMax.End = lv_Ymax.Substract(3, 0);
        }

        private void RefreshItems()
        {
            f_Point.Position = new PointF(ToScreen_X(3), ToScreen_Y(5));
        }

        /* ------------------------------------------------------------------------------------------ */

        //protected override void OnPaint(PaintEventArgs e)
        //{
        //    //e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        //    //e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        //    base.OnPaint(e);
        //}


        protected override void OnResize(EventArgs e)
        {
            RefreshAxis();
            RefreshItems();

            base.OnResize(e);
            Invalidate();
        }

        /* ------------------------------------------------------------------------------------------ */

        public PointObject AddPoint(PointF point)
        {
            var lv_PointObject = new PointObject(this);

            return lv_PointObject;
        }

        public void AddPlotterObject(PlotterObject obj)
        {

        }

        /* ------------------------------------------------------------------------------------------ */
    }
}
